// CICP by Arkowitz

integer primId = -1;
integer graphId = 0;
integer menuPrimId;
integer menuSrcPrimId;
integer menuMode = FALSE;
integer listenForMenu = 0;
integer menuNodeId;
list menuList;
list menuListOptions;
list menuIdList;
list menuStarts;
list menuEnds;
list masterMenuNameList;
list masterMenuIdList;
string strMenuStarts;
string strMenuEnds;
string strMasterMenuNameList;
string strMasterMenuIdList;
string strMenuIdList;
integer mode = 1;
integer touchMode = 1;
integer menuItemCount;
vector origPos;
vector origScale;
vector origColor;
rotation origRot;

integer elevX = FALSE;
integer elevY = FALSE;
integer elevZ = FALSE;
integer elevMe = FALSE;
integer elevOn = FALSE;

string strTitle;
string strUrlTitle;
string nodeMenuId;
string shapeType;
vector vparm1;
vector vparm2;
integer xInd;
integer yInd;
integer zInd;
integer nodeId;
integer isOrigin = FALSE;
integer isLegend = FALSE;
float normColorR;
float normColorG;
float normColorB;
float highColorR;
float highColorG;
float highColorB;
float fAlpha;
float sphereRad;
float height;
float myCell_size;
integer titleOn = FALSE;

integer menuLength;
integer menuStartItem;
integer menuEndItem;
key menuKey;
string menuMessage;

key cardKey;
integer configDone = 0;
string saveUrlFrag;
key curReq;
string cicp_host;
list http_list = [];
integer newGraphId = 0;
list selectedNodes;
integer graphListenCB = 0;
float fetchInterval = 0;
integer isMod = FALSE;

warpPos ( vector destpos ) {
   //R&D by Keknehv Psaltery, ~05/25/2006
   //with a little pokeing by Strife, and a bit more
   //some more munging by Talarus Luan
   //Final cleanup by Keknehv Psaltery
   vector startpos = llGetPos();
   // Limit to max setpos height
   if (destpos.z > 768)
       destpos.z = 768;
   // Compute the number of jumps necessary
   integer jumps = (integer)(llVecDist(destpos, startpos) / 10.0) + 1;
   // Try and avoid stack/heap collisions
   if (jumps > 100 )
       jumps = 100;    //  1km should be plenty
   integer count = 2;
   list rules = [ PRIM_POSITION, destpos ];  //The start for the rules list
   while (count < jumps) {
       rules = (rules=[]) + rules + rules;   //should tighten memory use.
       count = count << 1;
   }
   llSetPrimitiveParams(rules + llList2List( rules, (count - jumps)
<< 1, count) );
}

toggleTitle () {
    string titleSet;
    if ( titleOn ) {
        titleSet = ""; 
        titleOn = FALSE;
    }
    else {
        titleSet = strTitle;
        titleOn = TRUE;
    }
    llSetText(titleSet, <0,1,0>, .8);
}

resetHigh () {
    titleOn = TRUE;
    toggleTitle();
    llSetPrimitiveParams([PRIM_GLOW, ALL_SIDES, 0.0]);
    llSetColor(<normColorR, normColorG, normColorB>, ALL_SIDES);
    elevX = FALSE;
    elevY = FALSE;
    elevZ = FALSE;
    elevMe = FALSE;
    elevOn = FALSE;
}

upDown ( integer dim ) {
    if ( dim == 2 )
        elevY = !elevY;
    else if ( dim == 1 )
        elevX = !elevX;
    else if ( dim == 3 )
        elevZ = !elevZ;
    else {
        //if ( elevMe )
            //toggleTitle();
        elevMe = !elevMe;
    }
        
    if ( elevX || elevY || elevZ || elevMe ) {
        llSetColor(<highColorR, highColorG, highColorB>, ALL_SIDES);
        llSetPrimitiveParams([PRIM_GLOW, ALL_SIDES, 0.4]);
    }
    else {
        llSetColor(<normColorR, normColorG, normColorB>, ALL_SIDES);
        llSetPrimitiveParams([PRIM_GLOW, ALL_SIDES, 0.0]);
    }
}

toggleElev () {
    if ( elevOn ) {
        llRegionSay(-1000000 - graphId, "delev," + (string)xInd + "," +
                              (string)yInd + "," + (string)zInd);
        elevOn = FALSE;
    }
    else {
        llRegionSay(-1000000 - graphId, "elev," + (string)xInd + "," +
                              (string)yInd + "," + (string)zInd);
        elevOn = TRUE;
    }
}

configNode ( string propLine ) {
    list propList = llParseString2List(propLine, [","], []);
    strUrlTitle = llList2String(propList, 17);
    strTitle = llUnescapeURL(strUrlTitle);
    shapeType = llList2String(propList, 0);
    
    vparm1 = <(float)llList2String(propList, 1),
              (float)llList2String(propList, 2),
              (float)llList2String(propList, 3)>;
    vparm2 = <(float)llList2String(propList, 4),
              (float)llList2String(propList, 5),
              (float)llList2String(propList, 6)>;
    
    zInd = (integer)llList2String(propList, 16);
    yInd = (integer)llList2String(propList, 15);
    xInd = (integer)llList2String(propList, 14);
    if ( xInd == 0 && yInd == 0 && zInd == 0 )
        isOrigin = TRUE;
    else if ( (yInd == 0 && xInd == 0) ||
              (yInd == 0 && zInd == 0) ||
              (xInd == 0 && zInd == 0) )
        isLegend = TRUE;

    normColorR = (float)llList2String(propList, 7);
    normColorG = (float)llList2String(propList, 8);
    normColorB = (float)llList2String(propList, 9);
    highColorR = (float)llList2String(propList, 11);
    highColorG = (float)llList2String(propList, 12);
    highColorB = (float)llList2String(propList, 13);
    fAlpha = (float)llList2String(propList, 10);
    
    if ( shapeType == "sph" )
        sphereRad = (float)llList2String(propList, 5);        
    myCell_size = (float)llList2String(propList, 4) * 2;
    height = (float)llList2String(propList, 5);
    if ( shapeType == "rec" && height == 0 )
        llOwnerSay("got height of 0: " + propLine);

    nodeMenuId = llList2String(propList, 18);
    nodeId = (integer)llList2String(propList, 19);
    menuPrimId = (integer)llList2String(propList, 20);
    graphId = (integer)llList2String(propList, 21);
    if ( graphListenCB != 0 )
        llListenRemove(graphListenCB);
    graphListenCB = llListen(-1000000 - graphId, "", "", "");
    touchMode = 1;
    modSelf();
}

modSelf () {
    vector sizeVec = llGetScale();
    vector newPos = vparm1;
    
    if ( shapeType == "sph" ) {
        llSetPrimitiveParams([PRIM_TYPE, PRIM_TYPE_SPHERE, 0, <0.0, 1.0, 0.0>, 0.0, <0.0, 0.0, 0.0>, <0.0, 1.0, 0.0>]);
        sizeVec.z = sphereRad * 2;
        sizeVec.y = sphereRad * 2;
        sizeVec.x = sphereRad * 2;
    }
    else if ( shapeType == "lin" ) {
        llSetPrimitiveParams([PRIM_TYPE, PRIM_TYPE_CYLINDER, 0, <0.0, 1.0, 0.0>, 0.0, <0.0, 0.0, 0.0>, <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>]);
        newPos = vparm1 + ((vparm2 - vparm1) / 2);
        sizeVec.z = llVecDist(vparm1, vparm2);
        sizeVec.x = .02;
        sizeVec.y = .02;
        rotation rot = llRotBetween(<0, 0, 1>, llVecNorm(vparm2 - vparm1));
        llSetRot(rot);
    }
    else {
        llSetPrimitiveParams([PRIM_TYPE, PRIM_TYPE_BOX, 0, <0.0, 1.0, 0.0>, 0.0, <0.0, 0.0, 0.0>, <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>]);
        newPos.z += height / 2;   
    
        if ( height < .01 && height > -.01 )
            height = .01;
        if ( height < 0 )
            sizeVec.z = height * -1;
        else
            sizeVec.z = height;
        sizeVec.y = myCell_size;
        sizeVec.x = myCell_size;
    }
    
    llSetColor(<normColorR, normColorG, normColorB>, ALL_SIDES);
    llSetScale(sizeVec);
    llSetAlpha(fAlpha, ALL_SIDES);
    warpPos(newPos);
    llSetObjectDesc(strTitle);
}

initMenuVars () {
    strMenuIdList = "";
    strMenuStarts = "";
    strMenuEnds = "";
    strMasterMenuNameList = "";
    strMasterMenuIdList = "";
    menuItemCount = 0;
    selectedNodes = [];
}

processDispatchLine ( string line ) {
    //llOwnerSay("prim " + (string)primId + " dispatching: " + line);
    integer targPrimId = 0;
    if ( line == "more" )
        curReq = llHTTPRequest(cicp_host + "type=dispatch&layer=" +
                               (string)newGraphId, http_list, "");
    else if ( line == "new" )
        isMod = FALSE;
    else if ( line == "mod" )
        isMod = TRUE;
    else if ( llGetSubString(line, 0, 4) == "error" )
        llOwnerSay(line);
    else if ( line != "ok" ) {
        list lineList = llParseString2List(line, [";"], []);
        targPrimId = (integer)llList2String(lineList, 0);
        if ( newGraphId == 0 ) {
            list subList = llParseString2List(llList2String(lineList, 1),
                                              [","], []);
            newGraphId = (integer)llList2String(subList, 1);
            //llOwnerSay("prim " + (string)primId + " sending delete for graph " + (string)newGraphId + " first targ prim " + (string)targPrimId);
            if ( !isMod ) {
                llRegionSay(-1000000 - newGraphId, "del");
                llSleep(.5);
            }
        }
        if ( targPrimId == 0 )
            llOwnerSay((string)primId + " dispatch got targ 0 from " + line);
        llRegionSay(targPrimId * -1, llList2String(lineList, 1));
    }
    //return targPrimId;
}

processMenuLine ( string line ) {
    //llOwnerSay("prim " + (string)primId + " menu: " + line);
    if ( line == "more" )
        curReq = llHTTPRequest(cicp_host + "type=menu&layer=" +
                               (string)graphId, http_list, "");
    else if ( line == "nodes" ) {
        menuIdList = llParseString2List(strMenuIdList, [","], []);
        menuStarts = llParseString2List(strMenuStarts, [","], []);
        menuEnds = llParseString2List(strMenuEnds, [","], []);
        masterMenuIdList = llParseString2List(strMasterMenuIdList, [","], []);
        masterMenuNameList = llParseString2List(strMasterMenuNameList, [","], []);
        mode = 4;
    }
    else if ( mode == 4 ) {
        configNode(line);
        if ( fetchInterval != 0 )
            fetchNext();
    }
    else {
        list lineList = llParseString2List(line, [","], []);
        string firstPiece = llList2String(lineList, 0);
        if ( firstPiece == "menu" ) {
            if ( strMenuIdList != "" ) {
                strMenuIdList += ",";
                strMenuStarts += ",";
            }
            strMenuIdList += llList2String(lineList, 1);
            strMenuStarts += (string)menuItemCount;
        }
        else if ( firstPiece == "endmenu" ) {
            if ( strMenuEnds != "" )
                strMenuEnds += ",";
            strMenuEnds += (string)menuItemCount;
        }
        else {
            menuItemCount++;
            if ( strMasterMenuNameList != "" ) {
                strMasterMenuNameList += ",";
                strMasterMenuIdList += ",";
            }
            string mname = llList2String(lineList, 2);
            if ( llStringLength(mname) > 24 )
                mname = llGetSubString(mname, 0, 23);
            strMasterMenuNameList += mname;
            strMasterMenuIdList += llList2String(lineList, 0);
            if ( firstPiece == "-5" ) {
                fetchInterval = (float)llList2String(lineList, 1) * -1;
            }
        }
    }       
}

addToSelected ( list lineList ) {
    selectedNodes += [llList2String(lineList, 1)];
}

removeFromSelected ( integer nodeId ) {
    integer nodeInd = llListFindList(selectedNodes, [(string)nodeId]);
    if ( nodeInd != -1 )
        selectedNodes = llDeleteSubList(selectedNodes, nodeInd, nodeInd);
}

doMenu ( list lineList ) {
    integer menuInd = llListFindList(menuIdList, [llList2String(lineList, 5)]);
    //llOwnerSay("doMenu with {" + (string)lineList + "}, " + (string)menuIdList);
    if ( menuInd != -1 ) {
        // need a list for ids and a list for names
        menuList = llList2List(masterMenuNameList, 
                               (integer)llList2String(menuStarts, menuInd),
                               (integer)llList2String(menuEnds, menuInd));
        menuListOptions = llList2List(masterMenuIdList, 
                               (integer)llList2String(menuStarts, menuInd),
                               (integer)llList2String(menuEnds, menuInd));                            menuNodeId = (integer)llList2String(lineList, 2);
        removeFromSelected(menuNodeId);
        menuSrcPrimId = (integer)llList2String(lineList, 1);
        
        menuLength = llGetListLength(menuList);
        menuStartItem = 0;
        menuKey = llList2Key(lineList, 3);
        menuMessage = llUnescapeURL(llList2String(lineList, 4));
        doMenuDialog();
    }
}

doMenuDialog () {
    list tempMenuList;
    if ( menuLength - menuStartItem > 12 ) {
        menuEndItem = menuStartItem + 10;
        tempMenuList = llList2List(menuList, menuStartItem, menuEndItem);
        tempMenuList += "more>";
    }
    else {
        menuEndItem = menuLength;
        tempMenuList = llList2List(menuList, menuStartItem, menuEndItem);
    }
    llDialog(menuKey, menuMessage, tempMenuList, primId * -1);
    menuStartItem = menuEndItem + 1;
    listenForMenu = 1;
    llSetTimerEvent(10);
}

relayMenu ( integer menuInd ) {
    string menuOptionId = llList2String(menuListOptions, menuInd);
    if ( menuOptionId == "-1" ) {
        llRegionSay(menuSrcPrimId * -1, "high");
    }
    else if ( menuOptionId == "-2" )
        llRegionSay(-1000000 - graphId, "reset");
    else {
        mode = 3;
        string strMenuRelay = (string)graphId + "," + (string)menuNodeId + ",";
        strMenuRelay += menuOptionId;
        strMenuRelay += "," + (string)menuNodeId;
        integer i;
        for ( i = 0; i < llGetListLength(selectedNodes); i++ )
            strMenuRelay += ";" + llList2String(selectedNodes, i);
        newGraphId = 0;
        curReq = llHTTPRequest(cicp_host + "type=msg&msg=" + strMenuRelay +
                               "&region=" + llGetRegionName(), http_list, "");
        if ( menuOptionId == "-3" ) {
            llRegionSay(-1000000 - graphId, "del");
            reset();
        }
    }
}

fetchNext () {
    mode = 5;
    llSleep(fetchInterval);
    string strMenuRelay = (string)graphId + "," + (string)menuNodeId + ",-5";
    newGraphId = 0;
    //llOwnerSay("prim " + (string)primId + " fetching with " + strMenuRelay);
    curReq = llHTTPRequest(cicp_host + "type=msg&msg=" + strMenuRelay +
                           "&region=" + llGetRegionName(), http_list, "");
}

reset () {
    llSetScale(origScale);
    //llSetColor(origColor, ALL_SIDES);
    llSetRot(origRot);
    warpPos(origPos);
    menuMode = FALSE;
    mode = 1;
    isOrigin = FALSE;
    isLegend = FALSE;
    graphId = 0;
    resetHigh();
    if ( graphListenCB != 0 )
        llListenRemove(graphListenCB);
    fetchInterval = 0;
}

configAndRequest () {
    if ( configDone == 0 )
        cardKey = llGetNotecardLine("cicp_config", 0);
    else {
        //llOwnerSay("prim " + (string)primId + " saveUrlFrag: " + saveUrlFrag);
        curReq = llHTTPRequest(cicp_host + saveUrlFrag, http_list, "");
    }
}

default {

    touch_start ( integer total_number ) {
        if ( isLegend )
            toggleElev();
        upDown(4);
        if ( touchMode == 2 ) {
            toggleTitle();
            touchMode = 1;
            string strMenu = "slct," + (string)primId + "," +
                             (string)nodeId + ",";
            strMenu += (string)llDetectedKey(0) + ",";
            strMenu += strUrlTitle + "," + nodeMenuId;
            //llOwnerSay("strMenu: " + strMenu);
            if ( primId == menuPrimId ) {
                list lineList = llParseString2List(strMenu, [","], []);
                doMenu(lineList);
            }
            else
                llRegionSay(menuPrimId * -1, strMenu);
       }
       else if ( touchMode == 1 ) {
            toggleTitle();
            touchMode = 2;
            llRegionSay(menuPrimId * -1, "add," + (string)nodeId);
       }
    }
    
    on_rez ( integer in_primId ) {
        primId = in_primId;
        origPos = llGetPos();
        origScale = llGetScale();
        origColor = llGetColor(ALL_SIDES);
        origRot = llGetRot();
        llListen(0, "", llGetOwner(), "");
        llListen(primId * -1, "", "", "");
    }
    
    listen ( integer channel, string name, key id, string message ) {
        integer menuInd;
        if ( channel == 0 ) {
            if ( message == "totem_alldie" )
                llDie();
            else if ( message == "totem_allreset" )
                reset();
        }
        else if ( channel == -1000000 - graphId ) {
            if ( message == "del" ) {
                //llOwnerSay("prim " + (string)primId + " resetting");
                reset();
            }
            else if ( message == "reset" ) {
                if ( !isOrigin )
                    resetHigh();
            }
            else {
                list graphMsgList = llParseString2List(message, [","], []);
                string cmd = llList2String(graphMsgList, 0);
                integer cmdX = (integer)llList2String(graphMsgList, 1);
                integer cmdY = (integer)llList2String(graphMsgList, 2);
                integer cmdZ = (integer)llList2String(graphMsgList, 3);
                if ( !isLegend && !isOrigin && ((cmdY == yInd && cmdY != 0) ||
                                                (cmdX == xInd && cmdX != 0) ||
                                                (cmdZ != 0 && cmdZ == zInd)) ) {
                    if ( cmd == "elev" || cmd == "delev" ) {
                        if ( cmdY == yInd )
                            upDown(2);
                        else if ( cmdX == xInd )
                            upDown(1);
                        else
                            upDown(3);
                    }
                }
            }
        }
        else if ( channel == primId * -1 ) {
            //llOwnerSay("prim " + (string)primId + ": " + message);
            if ( channel == -1 && message == "ping" )
                llRegionSay(-999999, "pong");
            else if ( listenForMenu == 1 ) {
                if ( message == "more>" )
                    doMenuDialog();
                else {
                    menuInd = llListFindList(menuList, [message]);   
                    if ( menuInd != -1 )
                        relayMenu(menuInd);
                }
            }
            else {
                list lineList = llParseString2List(message, [","], []);
                string prefix = llList2String(lineList, 0);
                if ( prefix == "menu" ) {
                    graphId = (integer)llList2String(lineList, 1);
                    mode = 1;
                    initMenuVars();
                    saveUrlFrag = "type=menu&layer=" + (string)graphId;
                    configAndRequest();
                }
                else if ( prefix == "chunk" ) {
                    graphId = (integer)llList2String(lineList, 1);
                    integer chunkId = (integer)llList2String(lineList, 2);
                    mode = 2;
                    saveUrlFrag = "type=chunk&layer=" + (string)graphId +
                                  "&chunk=" + (string)chunkId;
                    configAndRequest();
                }                
                else if ( prefix == "slct" )
                    doMenu(lineList);
                else if ( prefix == "add" )
                    addToSelected(lineList);
                else if ( prefix == "high" ) {
                    if ( isLegend )
                        toggleElev();
                    upDown(4);
                }
                else {
                    //if ( primId == 156 )
                    //    llOwnerSay("prim " + (string)primId + " config: " + message);
                    configNode(message);
                }
            }
        }
    }

    http_response ( key request_id, integer status, list metadata,
string body ) {
        if ( request_id != curReq )
            return;
        //llOwnerSay(body);
        //llOwnerSay("status: " + (string)status);
        list bodyList = llParseString2List(body, ["\n"], []);
        integer i;
        for ( i = 0; i < llGetListLength(bodyList); i++ ) {
            string line = llList2String(bodyList, i);
            if ( mode == 1 || mode == 4 ) {
                processMenuLine(line);
            }
            else if ( mode == 2 ) {
                list subList = llParseString2List(line, [";"], []);
                string subLine = llList2String(subList, 1);
                integer targPrimId = (integer)llList2String(subList, 0);
                if ( targPrimId == primId )
                    configNode(subLine);
                else {
                    //llOwnerSay("send config to " + (string)targPrimId +
                    //           ": " + subLine);
                    if ( targPrimId == 0 )
                        llOwnerSay((string)primId + " mode2 got targ 0 from " + subLine);
                    llRegionSay(targPrimId * -1, subLine);
                }
            }
            else if ( mode == 3 || mode == 5 ) {
                processDispatchLine(line);
                if ( i == llGetListLength(bodyList) - 1 && line != "more" ) {
                    if ( newGraphId == graphId && !isMod )
                        reset();
                    if ( mode == 5 )
                        fetchNext();
                }
            }
        }
    }
            
    timer () {
        listenForMenu = 0;
        llSetTimerEvent(0);
    }
    
    dataserver ( key queryId, string data ) {
        if ( queryId == cardKey ) {
            cicp_host = data;
            if ( llGetSubString(cicp_host, 0, 4) == "https" )
                http_list = [HTTP_VERIFY_CERT, FALSE];
            configDone = 1;
            configAndRequest();
        }
    }
}
